﻿Imports System.Globalization

Namespace Compilers
    Friend Module Commons
        ' Common code that can be used by any compiler

        ''' <summary>
        ''' Used by the compilers to write debug information to the console
        ''' </summary>
        ''' <param name="text"></param>
        ''' <remarks></remarks>
        Public Sub AddLogC(ByVal text As String)
            AddLogV("[COMPILER] " & text)
        End Sub
        Public Sub AddLogC()
            AddLogV("")
        End Sub

        Public Structure PackageCompilerSimulationResult
            Public Code As PackageCompilerSimulationResultCode
            Public Message As String
        End Structure
        Public Enum PackageCompilerSimulationResultCode As Byte
            SimulationSucceeded = 0
            ioCantOpenSourceFile = 1
            ioCantOpenDestinationFile = 2
            ioSourceFileEmpty = 3
            NoSourceFiles = 4
            NoTargetFile = 5
            InvalidPackageFlags = 6
            InsuficientFreeSpace = 7
            CompilerException = 8
        End Enum
        Public Structure PackageCompilationStatistics
            ' THIS DOCUMENTATION MAY BE OBSOLETE
            Public PackedFiles As Long ' Stores the ammount of files readed and packed
            Public PackedBytes As ULong ' Stores the ammount of bytes readed from the sourcefiles, this also stores the size of the Datablock
            Public OutputSize As ULong ' Stores the total size of the output package
            Public StartTime As Date ' Stores the exact time when the operation started
            Public EndTime As Date ' Stores the exact time then the operation ends
            Public TcsDiffTime As String ' Stores an optional TCS difftime between StartTime and EndTime
            Public AverageBufferUsed As ULong ' Stores the average buffer size used when reading data from sourcefiles
            Public MaxBufferUsed As ULong ' Stores the max ammount of buffer used when reading data from sourcefiles
            Public MinBufferUsed As ULong ' Stores the min ammount of buffer used when reading data from sourcefiles
            Public AverageTicksPerIO As ULong ' Stores the average ammount of ticks that each IO (read) operation took
            Public MaxTicksPerIO As ULong ' Stores the max ammount of ticks that an IO (read) operation took
            Public MinTicksPerIO As ULong ' Stores the min ammount of ticks that an IO (read) operation took
            Public TickBarrier As Integer ' Stores the barrier or Limit that has been used for every tick operation, if there is more ticks than this value, the buffer will be reduced, if the ticks count is far from this value, the buffer will be increased
            Public TickLength As Long ' Stores the ammount of time (in ms) that each tick last
            Public BufferSteeping As Long ' Stores the ammount of variation that will be added or substracted from the buffer size used depending on the Tick counts
        End Structure
        Public Structure PackageCompilationResult
            Public Code As PackageCompilationResultCode
            Public Message As String
            Public Statistics As PackageCompilationStatistics
        End Structure
        Public Enum PackageCompilationResultCode As Byte
            CompilationSucceeded = 0
            ioCantOpenSourceFile = 1
            ioCantOpenDestinationFile = 2
            ioSourceFileEmpty = 3
            NoSourceFiles = 4
            NoTargetFile = 5
            InvalidPackageFlags = 6
            InsuficientFreeSpace = 7
            SimulationException = 8
            CompilationAborted = 9
            Reserved0 = 10
            Reserved1 = 11
            Reserved2 = 12
            Reserved3 = 13
            Reserved4 = 14
            CompilerException = 15
        End Enum

        Public Function CalculateLatencyTime() As TimeSpan
            Dim time1, time2, time3, time4, time5 As TimeSpan
            Dim startTime, endTime As Date
            Dim mathOperation As Long

            startTime = Now
            mathOperation = Fix(16 * 8 ^ 2 * 3.14159 + 1 / 2 - 14)
            endTime = Now

            time1 = endTime - startTime

            startTime = Now
            mathOperation = Fix(16 * 8 ^ 2 * 3.14159 + 1 / 2 - 14)
            endTime = Now

            time2 = endTime - startTime

            startTime = Now
            mathOperation = Fix(16 * 8 ^ 2 * 3.14159 + 1 / 2 - 14)
            endTime = Now

            time3 = endTime - startTime

            startTime = Now
            mathOperation = Fix(16 * 8 ^ 2 * 3.14159 + 1 / 2 - 14)
            endTime = Now

            time4 = endTime - startTime

            startTime = Now
            mathOperation = Fix(16 * 8 ^ 2 * 3.14159 + 1 / 2 - 14)
            endTime = Now

            time5 = endTime - startTime

            Dim finalTime As TimeSpan
            Dim finalTicks As Long
            finalTicks = time1.Ticks + time2.Ticks + time3.Ticks + time4.Ticks + time5.Ticks
            finalTicks = finalTicks / 5
            finalTime = New TimeSpan(finalTicks)

            Return finalTime
        End Function

        Public Function GetFormattedTime(ByVal _timespan As TimeSpan) As String
            Dim daysSuffix As String = " Days."
            Dim hoursSuffix As String = " h."
            Dim minutesSuffix As String = " m."
            Dim secondsSuffix As String = " s."
            Dim millisecondsSuffix As String = " ms."

            Dim output As String = ""

            If Not _timespan.Days = 0 Then output = _timespan.Days & daysSuffix & " "
            If Not _timespan.Hours = 0 Then output &= _timespan.Hours & hoursSuffix & " "
            If Not _timespan.Minutes = 0 Then output &= _timespan.Minutes & minutesSuffix & " "
            If Not _timespan.Seconds = 0 Then output &= _timespan.Seconds & secondsSuffix & " "
            If Not _timespan.Milliseconds = 0 Then output &= _timespan.Milliseconds & millisecondsSuffix

            output = Trim(output)

            If output = "" Then output = "0" & millisecondsSuffix

            Return output
        End Function

        Public Function FormatSizeAsHumanReadable(ByVal Size As ULong) As String
            If Size > 1073741824 Then ' 1 GiB then
                Dim downSampled As Single
                downSampled = Size / 1073741824
                Dim Output As String
                Dim splitted() As String
                splitted = Split(downSampled.ToString(System.Globalization.CultureInfo.InvariantCulture), ".")
                If Not splitted.Length = 2 Then Return downSampled & " GiB."
                Output = splitted(0) & "." & Left(splitted(1), 2) & " GiB."
                Return Output
            End If
            If Size > 1048576 Then ' 1 MiB then
                Dim downSampled As Single
                downSampled = Size / 1048576
                Dim Output As String
                Dim splitted() As String
                splitted = Split(downSampled.ToString(System.Globalization.CultureInfo.InvariantCulture), ".")
                If Not splitted.Length = 2 Then Return downSampled & " MiB."
                Output = splitted(0) & "." & Left(splitted(1), 2) & " MiB."
                Return Output
            End If
            If Size > 1024 Then ' 1 KiB then
                Dim downSampled As Single
                downSampled = Size / 1024
                Dim Output As String
                Dim splitted() As String
                splitted = Split(downSampled.ToString(System.Globalization.CultureInfo.InvariantCulture), ".")
                If Not splitted.Length = 2 Then Return downSampled & " KiB."
                Output = splitted(0) & "." & Left(splitted(1), 2) & " KiB."
                Return Output
            End If
            Return Size & " Bytes."
        End Function

        Public Function GetHexDump(ByVal buffer() As Byte) As String
            ' Returns an hex dump of the specified buffer

            Dim i As Integer
            Dim output As New System.Text.StringBuilder((buffer.Length * 2) + 16)
            For i = 0 To buffer.Length - 1
                If buffer(i) < 16 Then
                    output.Append("0")
                End If
                output.Append(Hex(buffer(i)))
            Next

            Return UCase(output.ToString())
        End Function

        Public ReadOnly Property ArphoonStaticSignature As Byte()
            Get
                ' ^Arphoon//Arps??
                Dim output() As Byte = {&H5E, &H41, &H72, &H70, &H68, &H6F, &H6F, &H6E, &H2F, &H2F, &H41, &H72, &H70, &H73, &H0, &H0}
                Return output
            End Get
        End Property

        Public Sub InsertIntoArray(ByRef buffer() As Byte, ByVal data As String)
            If data = "" Then Exit Sub
            InsertIntoArray(buffer, System.Text.Encoding.ASCII.GetBytes(data))
        End Sub
        Public Sub InsertIntoArray(ByRef buffer() As Byte, ByVal data As Byte)
            Dim byteArray() As Byte = {data}
            InsertIntoArray(buffer, byteArray)
        End Sub
        Public Sub InsertIntoArray(ByRef buffer() As Byte, ByVal data() As Byte)
            If buffer Is Nothing Then
                If data IsNot Nothing Then
                    buffer = data
                Else
                    Exit Sub
                End If
            End If

            If data Is Nothing Then Exit Sub

            Dim lastItem As Long = buffer.Length
            ReDim Preserve buffer(buffer.Length + data.Length - 1)
            Array.ConstrainedCopy(data, 0, buffer, lastItem, data.Length)
        End Sub

        ''' <summary>
        ''' Converts an Address inside an Unsigned Long to its 4 bytes Array in Candy Format representation
        ''' </summary>
        ''' <param name="value">Unsigned Long that will be converted to a Candy Value</param>
        ''' <returns>Candy value in a 4 Bytes Array</returns>
        ''' <remarks></remarks>
        Public Function GetCandyArrayFromUnsignedLong(ByVal value As ULong) As Byte()
            ' A Candy Address is just an scrambled 4 Bytes unsigned long value
            ' Unsigned Longs are stored in 4 Bytes as follows:
            '   00    00    00    00 : RAW Data
            '   1     2     3     4  : Octect  
            ' And a Candy address is stored as follows:
            '   00    00    00    00 : RAW Data
            '   3     4     1     2  : Octect  
            ' So, the Candy conversion from an unsigned value 06 04 FA D9 is: FA D9 06 04

            If value < 256 Then Return {Byte.Parse(0, CultureInfo.InvariantCulture), Byte.Parse(value, CultureInfo.InvariantCulture), Byte.Parse(0, CultureInfo.InvariantCulture), Byte.Parse(0, CultureInfo.InvariantCulture)}

            Dim octect1 As Byte = 0
            Dim octect2 As Byte = 0
            Dim octect3 As Byte = 0
            Dim octect4 As Byte = 0

            Dim hOctect1 As String = ""
            Dim hOctect2 As String = ""
            Dim hOctect3 As String = ""
            Dim hOctect4 As String = ""

            Dim hValue As String = Hex(value)
            If hValue.Length > 8 Then hValue = Right(hValue, 8)

            Do Until hValue.Length > 7
                hValue = "0" & hValue
            Loop

            hOctect1 = hValue.Substring(0, 2)
            hOctect2 = hValue.Substring(2, 2)
            hOctect3 = hValue.Substring(4, 2)
            hOctect4 = hValue.Substring(6, 2)

            octect1 = "&h" & hOctect3
            octect2 = "&h" & hOctect4
            octect3 = "&h" & hOctect1
            octect4 = "&h" & hOctect2

            Return {octect1, octect2, octect3, octect4}
        End Function

        ''' <summary>
        ''' Returns the next available shadow filename for the specified Arphoon Package that follows the provided naming convention
        ''' </summary>
        ''' <param name="packagename">Filename of the arphoon package for wich a shadow will be created</param>
        ''' <param name="convention">Filenaming convention for shadow packages</param>
        ''' <returns>The filename that must be used to create a new shadow package for the specified Arphoon Pakage following the naming convention provided</returns>
        ''' <remarks>This method assumes that convention is based on logical numeric increments of packages names. Also expects one asterisk symbol on the convention string. Example: file-*.sharpoon returns consecutive filenames starting from file-0.sharpoon</remarks>
        Public Function GetNextShadowFilenameForPackage(ByVal packagename As String, ByVal convention As String, ByVal startingIndex As Integer) As String
            If convention Is Nothing OrElse convention.Length < 1 Then
                convention = "-*.sharpoon"
            End If

            packagename = Trim(packagename)
            Dim fi As New System.IO.FileInfo(packagename)
            packagename = packagename.Substring(0, packagename.Length - fi.Extension.Length)

            Dim i As Integer
            Dim filename As String = Nothing
            Dim found As Boolean = False
            For i = startingIndex To startingIndex + 65535
                filename = packagename & convention.Replace("*", i)
                If Not System.IO.File.Exists(filename) Then
                    found = True
                    Exit For
                End If
            Next

            If Not found Then
                Throw New System.IO.FileNotFoundException("Cant find next available shadow filename. Consider consolidating package or increasing starting index")
            Else
                Return filename
            End If
        End Function

        Public Sub CloseStreams(ByRef stream As IO.FileStream, ByRef streamWritter As IO.BinaryWriter)
            On Error Resume Next

            streamWritter.Close()
            streamWritter = Nothing
            stream.Dispose()
            stream = Nothing
        End Sub

        Public Sub InsertArpsCode(ByRef buffer As System.Text.StringBuilder, ByVal instruction As String, ByVal parameter As String)
            instruction = Trim(instruction)

            Dim lineEnding As String = Chr(13) & Chr(10)
            Dim separator As String = Chr(4)

            If instruction = "" Then
                buffer.Append(lineEnding)
                Exit Sub
            End If

            If Not buffer.Length = 0 Then
                buffer.Append(lineEnding)
            End If

            buffer.Append(instruction)
            buffer.Append(separator)
            buffer.Append(parameter)
            'buffer &= instruction & separator & parameter
        End Sub
        Public Sub InsertArpsCode(ByRef buffer As System.Text.StringBuilder)
            InsertArpsCode(buffer, "", "")
        End Sub

        'Public Function AddLeadingChars(ByVal text As String, ByVal size As Integer, ByVal character As Char) As String
        '    If size = 0 Then Return ""
        '    If character = "" Then character = "0"

        '    If text = "" Then Return StrDup(size, character)
        '    If text.Length > size Then Return Left(text, size)

        '    Dim output As String
        '    output = StrDup(size - text.Length, character)
        '    output &= text

        '    If Not output.Length = size Then
        '        output = Right(output, size)
        '    End If

        '    Return output
        'End Function
    End Module
End Namespace